import BigInt
import EvmKit
import Foundation
import HsToolKit
import MarketKit
import OneInchKit
import SwiftUI

class OneInchMultiSwapProvider: BaseEvmMultiSwapProvider {
    private let kit: OneInchKit.Kit
    private let networkManager = Core.shared.networkManager
    private let evmFeeEstimator = EvmFeeEstimator()
    private let commission: Decimal? = AppConfig.oneInchCommission
    private let commissionAddress: String? = AppConfig.oneInchCommissionAddress

    init(kit: OneInchKit.Kit, storage: MultiSwapSettingStorage) {
        self.kit = kit

        super.init(storage: storage)
    }

    override var id: String {
        "1inch"
    }

    override var name: String {
        "1Inch"
    }

    override var icon: String {
        "1inch_32"
    }

    override func supports(tokenIn: MarketKit.Token, tokenOut: MarketKit.Token) -> Bool {
        guard tokenIn.blockchainType == tokenOut.blockchainType else {
            return false
        }

        switch tokenIn.blockchainType {
        case .ethereum, .binanceSmartChain, .polygon, .avalanche, .optimism, .arbitrumOne, .gnosis, .fantom, .base: return true
        default: return false
        }
    }

    override func quote(tokenIn: MarketKit.Token, tokenOut: MarketKit.Token, amountIn: Decimal) async throws -> IMultiSwapQuote {
        let blockchainType = tokenIn.blockchainType
        let chain = try evmBlockchainManager.chain(blockchainType: blockchainType)

        let addressFrom = try address(token: tokenIn)
        let addressTo = try address(token: tokenOut)

        guard let amount = tokenIn.rawAmount(amountIn) else {
            throw SwapError.invalidAmountIn
        }

        let quote = try await kit.quote(
            networkManager: networkManager,
            chain: chain,
            fromToken: addressFrom,
            toToken: addressTo,
            amount: amount,
            fee: commission
        )

        return await OneInchMultiSwapQuote(
            quote: quote,
            recipient: storage.recipient(blockchainType: blockchainType),
            slippage: slippage,
            allowanceState: allowanceState(token: tokenIn, amount: amountIn)
        )
    }

    override func confirmationQuote(tokenIn: MarketKit.Token, tokenOut: MarketKit.Token, amountIn: Decimal, transactionSettings: TransactionSettings?) async throws -> IMultiSwapConfirmationQuote {
        let blockchainType = tokenIn.blockchainType

        guard let evmKitWrapper = try evmBlockchainManager.evmKitManager(blockchainType: blockchainType).evmKitWrapper else {
            throw SwapError.noEvmKitWrapper
        }

        guard let gasPriceData = transactionSettings?.gasPriceData else {
            throw SwapError.noGasPriceData
        }

        guard let amount = tokenIn.rawAmount(amountIn) else {
            throw SwapError.invalidAmountIn
        }

        let evmKit = evmKitWrapper.evmKit
        let recipient = storage.recipient(blockchainType: blockchainType)
        let slippage = slippage

        let swap = try await kit.swap(
            networkManager: networkManager,
            chain: evmKit.chain,
            receiveAddress: evmKit.receiveAddress,
            fromToken: address(token: tokenIn),
            toToken: address(token: tokenOut),
            amount: amount,
            slippage: slippage,
            referrer: commissionAddress,
            fee: commission,
            recipient: recipient.flatMap { try? EvmKit.Address(hex: $0.raw) },
            gasPrice: gasPriceData.userDefined
        )

        let evmBalance = evmKit.accountState?.balance ?? 0
        let txAmount = swap.transaction.value
        let feeAmount = BigUInt(swap.transaction.gasLimit * gasPriceData.userDefined.max)
        let totalAmount = txAmount + feeAmount

        let insufficientFeeBalance = totalAmount > evmBalance

        let evmFeeData = try await evmFeeEstimator.estimateFee(
            evmKitWrapper: evmKitWrapper,
            transactionData: swap.transactionData,
            gasPriceData: gasPriceData,
            predefinedGasLimit: swap.transaction.gasLimit
        )

        return OneInchMultiSwapConfirmationQuote(
            swap: swap,
            recipient: recipient,
            slippage: slippage,
            insufficientFeeBalance: insufficientFeeBalance,
            evmFeeData: evmFeeData,
            nonce: transactionSettings?.nonce
        )
    }

    private func settingsView(tokenOut: MarketKit.Token, onChangeSettings: @escaping () -> Void) -> AnyView {
        let view = ThemeNavigationStack {
            RecipientAndSlippageMultiSwapSettingsView(tokenOut: tokenOut, storage: storage, slippageMode: .adjustable, onChangeSettings: onChangeSettings)
        }

        return AnyView(view)
    }

    override func settingsView(tokenIn _: MarketKit.Token, tokenOut: MarketKit.Token, quote _: IMultiSwapQuote, onChangeSettings: @escaping () -> Void) -> AnyView {
        let view = ThemeNavigationStack {
            RecipientAndSlippageMultiSwapSettingsView(tokenOut: tokenOut, storage: storage, slippageMode: .adjustable, onChangeSettings: onChangeSettings)
        }

        return AnyView(view)
    }

    override func settingView(settingId: String, tokenOut: MarketKit.Token, onChangeSetting: @escaping () -> Void) -> AnyView {
        if settingId == MultiSwapMainField.slippageSettingId {
            return settingsView(tokenOut: tokenOut, onChangeSettings: onChangeSetting)
        }

        return super.settingView(settingId: settingId, tokenOut: tokenOut, onChangeSetting: onChangeSetting)
    }

    override func swap(tokenIn: MarketKit.Token, tokenOut _: MarketKit.Token, amountIn _: Decimal, quote: IMultiSwapConfirmationQuote) async throws {
        guard let quote = quote as? OneInchMultiSwapConfirmationQuote else {
            throw SwapError.invalidQuote
        }

        guard let gasLimit = quote.evmFeeData?.surchargedGasLimit else {
            throw SwapError.noGasLimit
        }

        try await super.send(
            blockchainType: tokenIn.blockchainType,
            transactionData: quote.swap.transactionData,
            gasPrice: quote.swap.transaction.gasPrice,
            gasLimit: gasLimit,
            nonce: quote.nonce
        )
    }

    override func spenderAddress(chain: Chain) throws -> EvmKit.Address {
        try OneInchKit.Kit.routerAddress(chain: chain)
    }

    private func address(token: MarketKit.Token) throws -> EvmKit.Address {
        switch token.type {
        case .native: return try EvmKit.Address(hex: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")
        case let .eip20(address): return try EvmKit.Address(hex: address)
        default: throw SwapError.invalidAddress
        }
    }

    private var slippage: Decimal {
        storage.value(for: MultiSwapSettingStorage.LegacySetting.slippage) ?? MultiSwapSlippage.default
    }
}

extension OneInchMultiSwapProvider {
    enum SwapError: Error {
        case invalidAddress
        case invalidAmountIn
        case invalidQuote
        case noEvmKitWrapper
        case noGasLimit
        case noGasPriceData
    }
}

extension Swap {
    var transactionData: TransactionData {
        TransactionData(to: transaction.to, value: transaction.value, input: transaction.data)
    }
}
